usb: host: class: support for usb host hub class#99735
usb: host: class: support for usb host hub class#99735AidenHu wants to merge 6 commits intozephyrproject-rtos:mainfrom
Conversation
|
I think this hub class implementation can be good helper to be used for testing the new host stack of 94590 for multiple devices attachment and detachment. This hub PR can grow up with your 94590. I will keep updating it for this PR based on your newest changes for host stack. Hope your any comments here. |
josuah
left a comment
There was a problem hiding this comment.
Here is an incomplete review with a bit of feedback.
|
@josuah |
3bd611d to
35a2747
Compare
|
@josuah |
There was a problem hiding this comment.
If we look at the commit subsys: usb: host: add two APIs for connect/disconnect, it looks like the connect() and disconnect() functions are practically the same.
It is possible to declare the previous functions as public with a prototype in .h to avoid duplicating the code.
There was a problem hiding this comment.
If we look at the commit
subsys: usb: host: add two APIs for connect/disconnect, it looks like theconnect()anddisconnect()functions are practically the same.It is possible to declare the previous functions as public with a prototype in
.hto avoid duplicating the code.
Hi @josuah
I have created another commit #100072 about this requirement. Please help to review in this thread. So you can ignore subsys: usb: host: add two APIs for connect/disconnect in current thread.
| } | ||
|
|
||
| static void dev_connected_handler(struct usbh_context *const ctx, | ||
| const struct uhc_event *const event) |
There was a problem hiding this comment.
If this function needs to be used outside, by the HUB, then it is possible to remove the static, give it an API name such as usbh_device_connected(), then add it to usbh_device.h, as an example.
There was a problem hiding this comment.
@josuah
Thank you for your feedback.
I am thinking about this solution. If we change this function into a public API, there will be some structural issues. dev_connected_handler is tied to the event handler running in the USB bus thread, triggered by low-level events. Also, the speed information comes from the second parameter event. Making it an API would require dropping or modifying this parameter, which means extra changes.
Instead, if adding a new API in the commit “subsys: usb: host: add two APIs for connect/disconnect”, we can reuse the same logic as the caller to reduce code duplication and keep the structure clean.
This is my opinion, hope your any comment for this topic.
| static void dev_removed_handler(struct usbh_context *const ctx) | ||
| { |
There was a problem hiding this comment.
Likewise, if this function needs to be used outside, by the HUB code, then it is possible to remove the static, give it an API name such as usbh_device_remove(), then add it to usbh_device.h.
|
Understood, thank you! Here above is some short illustration of what was discussed in #99775 (comment) |
cb6a55e to
edb59fb
Compare
josuah
left a comment
There was a problem hiding this comment.
This is still an incomplete review but here is a few feedback in the meantime I can gather more time for a complete review.
Thank you for your patience!
edb59fb to
6836384
Compare
|
Have updated the basement from #99775 for this PR. |
6836384 to
6acdb74
Compare
d984e65 to
6b42426
Compare
6b42426 to
496905e
Compare
|
496905e to
7d47bb7
Compare
c57debb to
c79d27a
Compare
|
Thank you for the comment above. Expect something confusing about host hub feature (need your more clarification, thanks), I have checked the other all comments and updated code. For |
ea4d028 to
bdc7193
Compare
|
@jfischer-no |
This change extends the usb_device structure to keep track of its hub relationship, including the parent hub device, hub Think Time, port number, and topology level. Signed-off-by: Aiden Hu <weiwei.hu@nxp.com>
when the first device is attached, its level should be set as 1. Signed-off-by: Aiden Hu <weiwei.hu@nxp.com>
This change updates usb_hub.h by adding missing USB Hub class definitions from the specification, including hub class codes, descriptor types, status and change bits, and related data structures. The goal is to make the public header more complete and easier to use for USB hub implementations. Signed-off-by: Aiden Hu <weiwei.hu@nxp.com>
This change enhances USB_HostHelperGetPeripheralInformation() so the MCUX USB host driver can correctly report hub-related topology data, including the parent hub address, port number, nearest high speed hub, HS hub port, hub think time and level. The goal is to ensure proper device identification and routing, especially when full speed or low speed devices are connected behind multi level or high speed hubs. Signed-off-by: Aiden Hu <weiwei.hu@nxp.com>
bdc7193 to
6a76ea2
Compare
@tmon-nordic and @jfischer-no |
6a76ea2 to
2b4f8dc
Compare
|
Firstly, thank you much for the detailed review about this PR. Now based on the current information, I have done a adjustment. If I am wrong, please add your comment. |
|
@tmon-nordic |
2b4f8dc to
b11a44b
Compare
|
|
Please firstly ignore the CI run failure, it is caused by the removal of two functions from |
roma-jam
left a comment
There was a problem hiding this comment.
Hi @AidenHu,
I decided to review and share my ideas regarding the Hub feature.
There are couple of notes and one general idea about the architectural approach.
Maybe you will find them helpful.
Please do not hesitate to poke me, when something is not clear or any additional information from my side is required.
| @@ -96,6 +96,14 @@ struct usb_device { | |||
| struct usb_host_ep ep_out[16]; | |||
| /** Pointers to device IN endpoints */ | |||
| struct usb_host_ep ep_in[16]; | |||
| /** Pointer to the hub to which this device is connected */ | |||
| struct usb_device *hub; | |||
There was a problem hiding this comment.
From the architectural point of view, any hub device is the same usb device as anything else. So that means, that after connection, we already have it in the sys_dlist_t udevs.
What gives us the option to keep the pointer to the "parent", which can be the pointer to the another udev in the list in case the device is a downstream device.
For the device, connected to the root port, it is possible to keep this pointer NULL.
From the uhc logic, there should be no difference, when a new device is connected (to directly to the root port or via external hub port) and it might be handled with the same dev_connected_handler().
This flag (parent == NULL or parent == *another udev) might be used to build the device tree.
Maybe, it makes sense to think about the new abstraction object: device node, which represents the combination: parent + port number? Thus, the root node will be [NULL, 0], and any other node will be [*udev, port_num].
PS. As a bonus, if the hardware has two usb controllers, we can distinguish them by port number, when parent is NULL.
There was a problem hiding this comment.
@roma-jam
Thank you for this elegant architectural proposal! I agree that the hub pointer naturally represents the device tree structure, and your device_node abstraction of [parent, port] is conceptually clean. However, I'd like to respectfully point out that while hub devices are indeed USB devices from a data structure perspective, they differ fundamentally in their operational role: hub devices actively manage port events through continuous interrupt endpoint monitoring and complex state machines, whereas regular devices are passive. The current architecture already achieves the unification you're suggesting—both root port and hub port connections ultimately call the same usbh_device_connect() function with a properly initialized usb_device that has its hub and hub_port fields set correctly. The difference is in how the connection is detected (hardware interrupt for root ports vs. software polling for hub ports), not in how the device tree is represented. Regarding your PS about multiple controllers: the current implementation already supports this through separate usbh_context instances per controller, with events carrying the controller information via the event.dev field, so distinguishing controllers by port number when parent == NULL wouldn't be necessary
There was a problem hiding this comment.
The current architecture already achieves the unification you're suggesting—both root port and hub port connections ultimately call the same usbh_device_connect() function with a properly initialized usb_device that has its hub and hub_port fields set correctly
The one thing I still wonder about - what can be used during enqueuing to tell the driver, that this particular device is connected via hub?
Some drivers required this knowledge to configure the channel correctly.
To do this, and to handle this correctly, we need to be able to get the following params:
- parent port speed [speed of the hub]
- device speed [speed of the port, when it is enabled]
This is important to support combinations as High speed hubs + Low speed devices, connected to Hubs port.
Is there any way to get these params?
There was a problem hiding this comment.
Good question! The current implementation already provides access to both parameters you mentioned through the struct usb_device pointer:
Parent port speed (Hub's speed): Available via udev->hub->speed
Device speed: Available via udev->speed
The struct usb_device contains the complete topology information including the parent hub pointer. For example, in the MCUX driver, we use USB_HostHelperGetPeripheralInformation() to query these parameters:
// Get device speed
USB_HostHelperGetPeripheralInformation(udev, kUSB_HostGetDeviceSpeed, &device_speed);
// Get HS hub address (if device is LS/FS connected to HS hub)
USB_HostHelperGetPeripheralInformation(udev, kUSB_HostGetDeviceHSHubNumber, &hs_hub_addr);
// Get HS hub port
USB_HostHelperGetPeripheralInformation(udev, kUSB_HostGetDeviceHSHubPort, &hs_hub_port);
These helper functions traverse the udev->hub chain to find the high-speed hub and extract the necessary TT-related information for handling HS Hub + LS/FS device combinations. The driver can access all the topology information it needs through the device pointer to properly configure channels for different speed combinations.
| /** Pointer to the hub to which this device is connected */ | ||
| struct usb_device *hub; | ||
| /** Device's hub Think Time */ | ||
| uint16_t tt; |
There was a problem hiding this comment.
Maybe, it makes sense to introduce the whole hub-related part of the struct? Or to introduce them as "getters", because they are part of the Hub Descriptor that we need to request in the potential class driver?
Because, there are several other important hub characteristics, that we might be able to use in the driver.
Such as:
- number of ports (relevant, when we handle the port(s) connection)
- time from power on to power good (relevant, when hub can power on each port or all ports altogether)
- characteristics (tt includes here, as well as port logical switching and led support)
- current consumption
There was a problem hiding this comment.
Thank you for the comment here,
Actually previously the total uint16_t hub_characteristics; is added here, but @jfischer-no suggests keep think time here.
| /** Device's hub port */ | ||
| uint8_t hub_port; | ||
| /** Device's level (root device = 0) */ | ||
| uint8_t level; |
There was a problem hiding this comment.
Just out of curiosity, what is the purpose of having level in explicit form?
There was a problem hiding this comment.
The level field is essentially a topology depth indicator that helps the USB stack:
Enforce USB specification limits on hub chaining
Provide necessary information to the hardware controller driver
Maintain and validate the device tree structure
There was a problem hiding this comment.
This file seems like a candidate to be usb_ch11.h, not usb/class/usb_hub.h?
There was a problem hiding this comment.
Yes, agree, updated.
|
|
||
| LOG_DBG("USB HUB device probe at interface %u", target_iface); | ||
|
|
||
| desc_start = usbh_desc_get_iface(udev, target_iface); |
There was a problem hiding this comment.
Some hubs might have several interfaces with different TT. Especially on High speed configuration.
| HUB_RUN_CLEAR_DONE, | ||
| }; | ||
|
|
||
| enum usbh_hub_port_app_status { |
| hub_mgr.total_hubs++; | ||
| k_mutex_unlock(&hub_mgr.lock); | ||
|
|
||
| k_work_submit(&hub_mgr_data->hub_work.work); |
There was a problem hiding this comment.
There are 14 k_work_submit() throughout the code. Maybe, it might be done simpler, because usually the hub require port handling only after port status changed and ports have quite fixate determine states (according the the USB 2.0 Figure 11-10). So it allows to handle them one by one till they all are in any final state (powered off, disconnected [no device], connected [port has a device and device completed the reset] or disabled [overcurrent or unable to reset the device in port])
Maybe, with the following approach it might be simplified to two (or at elast three) k_work_sumbits:
-
k_work_submit(&hub_mgr_data->hub_work...): New hub appeared -> we need to get descriptor, configure the internal object and prepare ports for handling (allocate their objects and put in the list of pending ports), enable the interrupt EP IN to be able to catch the port or hub changes. (We need to be careful, as the hub already might have a device attached, so we can postpone enabling EP IN till we handle the port with a device - it might be another Hub to enable) -
k_work_submit(&hub_mgr_data->port_work...): Hubs port/ports have something, that needs to be handled. Usually, starts after the EP IN transfer complete. We can add he port to pending list and then the work might handle the pending port list till it is empty. Resubmit the transfer again when no more port to handle.
This might help to simplify the logic and make it more linear.
Otherwise it is easy to miss the important details or maintain the code IMHO.
|
|
||
| /* Port state enumeration */ | ||
| enum usbh_port_state { | ||
| PORT_STATE_DISCONNECTED, |
| int usbh_req_set_hcfs_ppwr(const struct usb_device *udev, | ||
| const uint8_t port); | ||
|
|
||
| int usbh_req_set_hcfs_prst(const struct usb_device *udev, | ||
| const uint8_t port); | ||
|
|
There was a problem hiding this comment.
This commit needs to be fixed, we cannot just remove these functions. I see you moved them to usbh_hub.[c,h], but IMO the right place would be usbh_ch11.[c,h]. What do you think?
Header include in the shell needs to be update as well. Also see my comment in the next commit.
| int usbh_req_desc_hub(struct usb_device *const udev, | ||
| uint8_t *const buffer, | ||
| const uint16_t len); | ||
|
|
||
| /* Hub features */ | ||
| int usbh_req_clear_hcfs_c_hub_local_power(struct usb_device *const udev); | ||
| int usbh_req_clear_hcfs_c_hub_over_current(struct usb_device *const udev); | ||
|
|
||
| /* Port features */ | ||
| int usbh_req_set_hcfs_penable(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_penable(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_set_hcfs_psuspend(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_psuspend(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_set_hcfs_prst(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_prst(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_set_hcfs_ppwr(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_ppwr(struct usb_device *const udev, const uint8_t port); | ||
|
|
||
| /* Port change features */ | ||
| int usbh_req_clear_hcfs_c_pconnection(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_c_penable(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_c_psuspend(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_c_pover_current(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_clear_hcfs_c_preset(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_set_hcfs_ptest(struct usb_device *const udev, const uint8_t port); | ||
| int usbh_req_set_hcfs_pindicator(struct usb_device *const udev, const uint8_t port); | ||
|
|
||
| int usbh_req_get_port_status(struct usb_device *const udev, | ||
| const uint8_t port_number, uint16_t *const wPortStatus, | ||
| uint16_t *const wPortChange); | ||
|
|
||
| int usbh_req_get_hub_status(struct usb_device *const udev, | ||
| uint16_t *const wHubStatus, | ||
| uint16_t *const wHubChange); |
There was a problem hiding this comment.
I suggest splitting this commit into two. The introduction of the chapter 11 control requests above should be moved to the previous commit (where you removed the two existing hub request). These changes would then be atomically and bisectable. After that, the commit that introduces hub class would be more clear.
|
@AidenHu, I am not sure you are on Discord, please ping me there. |
|
@jfischer-no and @roma-jam @jfischer-no |
@AidenHu Sorry, I cannot find it. I have started a new conversation, I hope it is you. |
Move hub control requests from usbh_ch9 to new usbh_ch11 files to align with USB 2.0 specification structure. Chapter 11 defines hub-specific requests which are distinct from the general USB device requests in Chapter 9. The new implementation provides all hub and port control requests defined in the specification, including feature control, status queries, and descriptor retrieval. This provides the foundation for the hub class driver implementation. Signed-off-by: Aiden Hu <weiwei.hu@nxp.com>
Add usbh_hub_mgr to enable connecting USB hubs and managing downstream devices through multi-tier hub topology. The hub manager implements two state machines for hub lifecycle management. The hub state machine handles hub enumeration, descriptor retrieval, and port power configuration. The port state machine manages device connection detection, port reset sequences, device enumeration coordination, and disconnection handling. Hub status changes are monitored through interrupt endpoint with automatic transfer resubmission. The implementation supports recursive hub topology with configurable chain depth limits. When a hub is removed, all downstream devices and child hubs are recursively disconnected to maintain topology consistency. This builds upon Chapter 11 control requests to provide complete hub class driver functionality in usbh_hub_mgr.c and usbh_hub_mgr.h. Signed-off-by: Aiden Hu <weiwei.hu@nxp.com>
b11a44b to
5a30c41
Compare
|









Dependency: